package com.kx.face.util;

import org.opencv.core.*;
import org.opencv.objdetect.CascadeClassifier;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.lang.reflect.Field;

/**
 * 脸的工具类
 * @Author Sun
 * @Date 2021/7/21 21:13
 */
public class FaceUtil {
    /**
     * 临时给jvm增加环境变量，获取path对象
     * @param path
     */
    private static void addDirToPath(String path){
        try{
            //反射classloader里面的sys_paths成员
            Field field=ClassLoader.class.getDeclaredField("sys_paths");
            //修改这个成员，添加一个目录进去
            //设置可见性
            field.setAccessible(true);
            //获取此变量的值
            String [] paths= (String[]) field.get(null);
            //创建一个新的数组，比paths多一个元素
            String [] temp=new String[paths.length+1];
            //数组复制
            System.arraycopy(paths,0,temp,0,paths.length);
            //指定最后一个元素，赋值成我们的参数
            temp[temp.length-1]=path;
            //设置classloader里面的sys_paths成员的值
            field.set(null,temp);
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * bufferedimage 转 mat
     * @param image 待转化的图片
     * @return 转化后的mat
     */
    public static Mat bufferedImage2Mat(BufferedImage image){
        //把bufferedimage转化成像素点
        byte [] pixels=((DataBufferByte)(image.getRaster().getDataBuffer())).getData();
        //建立一个mat
        Mat mat=new Mat(image.getHeight(),image.getWidth(), CvType.CV_8UC3);
        //把原本的bufferedimage里面的数据交给mat
        mat.put(0,0,pixels);
        return mat;
    }

    /**
     * mat转bufferedimage
     * @param mat 待转化的图片
     * @return 转化之后的BufferedImage
     */
    public static BufferedImage matToBufferedImage(Mat mat){
        //获取图片的宽高
        int width=mat.cols();
        int height=mat.rows();
        //获取mat的颜色通道
        int dims=mat.channels();
        //计算总像素数量
        int[] pixels=new int [width*height];
        //如果一个像素点有三个颜色通道，每一个颜色通道就是rgb其中之一，用一个字节表述rgb其中之一
        //于是，rgb一共需要3个字节
        //请问一共需要多少个字节，来表达width*height个像素点？
        byte [] rgbData=new byte[width*height*dims];

        //获取像素点的信息
        mat.get(0,0,rgbData);
        //申请一个BufferedImage
        BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);

        //像素点转化算法 *****
        int index=0;
        int r=0;
        int g=0;
        int b=0;
        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                //彩色照片
                if(dims==3){
                    //计算第index个像素与行和列的关系.
                    index=(row*width+col)*dims;
                    //拿到rgb
                    b=rgbData[index]&0xff;
                    g=rgbData[index+1]&0xff;
                    r=rgbData[index+2]&0xff;
                    //合成像素点
                    //三原色合成，怎么合成?
                    pixels[row*width+col]=(0xff<<24)|(r<<16)|(g<<8)|(b<<0);
                }

            }
        }
        //把像素点交给bufferedimage
        image.getRaster().setDataElements(0,0,width,height,pixels);
        return image;
    }

    /**
     * 判断一张图片有没有脸
     * @param image 待判断的图片
     * @return 图片中最主要的脸
     * 主要：脸的面积最大，就是最主要。
     */
    public static BufferedImage haveFace(BufferedImage image){
        //1、要加载dll文件到我们的path里
        addDirToPath("D:\\ideaWorkSpace\\five_stage\\automation\\face-service\\opencv");
        //2、装载目录的类库
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        //3、装载机器人，人脸检测的机器人
        CascadeClassifier faceDetector=new CascadeClassifier("D:\\ideaWorkSpace\\five_stage\\automation\\face-service\\opencv\\haarcascade_frontalface_alt.xml");

        //bufferedImage->mat
        //因为opencv用的图片是mat格式，而我们java拥有的是BufferedImage
        Mat mat=bufferedImage2Mat(image);
        //创建一个人脸区域的集合，若检测出来人脸，由矩形指出
        MatOfRect faceDetections=new MatOfRect();

        //让机器人检测人脸，把结果放入faceDetections中
        faceDetector.detectMultiScale(mat,faceDetections);
        //拿到每一个 人脸的 区域
        Rect[] rects=faceDetections.toArray();

        if(rects==null||rects.length==0){
            //无脸
            return null;
        }

        //最大的脸的范围
        Rect maxFace= null;
        int maxInt=0;
        for (int i = 0; i < rects.length; i++) {
            Rect rect=rects[i];
            if(maxInt<rect.width*rect.height){
                //这个脸更大
                maxInt=rect.width*rect.height;
                maxFace=rect;
            }
//            Imgproc.rectangle(mat,new Point(rect.x,rect.y),
//                    new Point(rect.x+rect.width,rect.y+rect.height),
//                    new Scalar(0,255,0),1);
        }

        //图片裁剪
        Mat face=new Mat(mat,maxFace);

        //返回结果
        return matToBufferedImage(face);
    }

    public static void main(String[] args) throws Exception {
        BufferedImage image= ImageIO.read(new File("C:\\Users\\孙晋超\\Pictures\\Saved Pictures\\4.jpg"));
        image=haveFace(image);
        ImageIO.write(image,"png",new File("C:\\Users\\孙晋超\\Pictures\\Saved Pictures\\h.jpg"));
    }

}
